﻿using log4net.Appender;
using log4net.Core;
using log4net.Layout;
using log4net.Util;
using Microsoft.SharePoint.Client;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;

namespace ZhongchenZhou.log4net.Appender.SP
{
    public class SharePoint2013Appender : AppenderSkeleton
    {
        #region Public Instance Constructors

		/// <summary> 
        /// Initializes a new instance of the <see cref="SharePoint2013Appender" /> class.
		/// </summary>
		/// <remarks>
		/// Public default constructor to initialize a new instance of this class.
		/// </remarks>
        public SharePoint2013Appender()
		{
            _parameters = new ArrayList();
            _listColumnsNameMapping = new Dictionary<string, string>();
		}

		#endregion // Public Instance Constructors

        #region Public Instance Properties

        public string SiteUrl { get; set; }

        public string ListName { get; set; }

        public string Domain { get; set; }

        public string Username { get; set; }

        public string Password { get; set; }

        #endregion // Public Instance Properties

        #region Override implementation of AppenderSkeleton

        override protected void OnClose()
        {
            base.OnClose();
            DiposeClientContext();
        }

        protected override void Append(global::log4net.Core.LoggingEvent loggingEvent)
        {
            InitialiseClientContext();

            InitialiseListColumnsNameMapping();

            var listItem = LoggingEventToListItem(loggingEvent);

            try
            {
                listItem.Update();

                _context.ExecuteQuery();

                LogLog.Debug(declaringType, string.Format("ListName [{0}] Id [{1}]", ListName, listItem.Id));
            }
            catch (Exception e)
            {
                ErrorHandler.Error("Exception while writing to SharePoint 2013 list.", e);
            }
        }

        #endregion

        #region Public Instance Methods

        public void AddParameter(SharePoint2013AppenderParameter parameter)
        {
            _parameters.Add(parameter);
        }

        #endregion // Public Instance Methods

        #region Private Instance Methods

        private void DiposeClientContext()
        {
            if (_context != null)
            {
                try
                {
                    _context.Dispose();
                }
                catch (Exception ex)
                {
                    LogLog.Warn(declaringType, "Exception while disposing cached ClientContext object", ex);
                }
                _context = null;
            }
        }

        private void InitialiseClientContext()
        {
            if (_context == null)
            {
                if (!string.IsNullOrWhiteSpace(SiteUrl))
                {
                    _context = new ClientContext(SiteUrl);

                    LogLog.Debug(declaringType, string.Format("SiteUrl [{0}] Domain [{1}] Username [{2}]", SiteUrl, Domain, Username));

                    if (!string.IsNullOrWhiteSpace(Domain) && !string.IsNullOrWhiteSpace(Username) && !string.IsNullOrWhiteSpace(Password))
                    {
                        _context.Credentials = new NetworkCredential(Username, Password, Domain);
                    }
                }
                else
                {
                    throw new LogException("SiteUrl is empty.");
                }
            }
        }

        private void InitialiseListColumnsNameMapping()
        {
            if (_listColumnsNameMapping.Count == 0)
            {
                if (string.IsNullOrWhiteSpace(ListName))
                {
                    throw new LogException("ListName is empty.");
                }

                LogLog.Debug(declaringType, string.Format("ListName [{0}]", ListName));

                List logList = _context.Web.Lists.GetByTitle(ListName);

                // Set the parameter values
                foreach (SharePoint2013AppenderParameter param in _parameters)
                {
                    var field = logList.Fields.GetByTitle(param.ParameterName);

                    _context.Load(field, f => f.InternalName);

                    try
                    {
                        _context.ExecuteQuery();

                        _listColumnsNameMapping.Add(param.ParameterName, field.InternalName);

                        LogLog.Debug(declaringType, string.Format("Field Title [{0}] Field Internal Name [{1}]", param.ParameterName, field.InternalName));
                    }
                    catch (Exception e)
                    {
                        throw new LogException("Exception while retrieving column internal name.", e);
                    }                    
                }
            }
        }

        private ListItem LoggingEventToListItem(LoggingEvent loggingEvent)
        {
            List logList = _context.Web.Lists.GetByTitle(ListName);

            // We are just creating a regular list item, so we don't need to 
            // set any properties. If we wanted to create a new folder, for 
            // example, we would have to set properties such as 
            // UnderlyingObjectType to FileSystemObjectType.Folder. 
            ListItemCreationInformation itemCreateInfo = new ListItemCreationInformation();
            ListItem listItem = logList.AddItem(itemCreateInfo);

            // Set the parameter values
            foreach (SharePoint2013AppenderParameter param in _parameters)
            {
                var fieldInternalName = _listColumnsNameMapping[param.ParameterName];

                var fieldValue = param.GetFormattedValue(loggingEvent);

                listItem[fieldInternalName] = fieldValue;
            }

            return listItem;
        }

        #endregion // Private Instance Methods

        #region Private Instance Fields

        private Dictionary<string, string> _listColumnsNameMapping;

        private ArrayList _parameters;

        private ClientContext _context;

        #endregion // Private Instance Fields

        #region Private Static Fields

        /// <summary>
        /// The fully qualified type of the SharePoint2013Appender class.
        /// </summary>
        /// <remarks>
        /// Used by the internal logger to record the Type of the
        /// log message.
        /// </remarks>
        private readonly static Type declaringType = typeof(SharePoint2013Appender);

        #endregion Private Static Fields
    }

    public class SharePoint2013AppenderParameter
    {
        #region Public Instance Properties

        /// <summary>
        /// Gets or sets the name of this parameter.
        /// </summary>
        /// <value>
        /// The name of this parameter.
        /// </value>
        /// <remarks>
        /// <para>
        /// The name of this parameter. The parameter name
        /// must match up to attribute name of entity.
        /// </para>
        /// </remarks>
        public string ParameterName { get; set; }

        /// <summary>
        /// Gets or sets the <see cref="IRawLayout"/> to use to 
        /// render the logging event into an object for this 
        /// parameter.
        /// </summary>
        /// <value>
        /// The <see cref="IRawLayout"/> used to render the
        /// logging event into an object for this parameter.
        /// </value>
        /// <remarks>
        /// <para>
        /// The <see cref="IRawLayout"/> that renders the value for this
        /// parameter.
        /// </para>
        /// <para>
        /// The <see cref="RawLayoutConverter"/> can be used to adapt
        /// any <see cref="ILayout"/> into a <see cref="IRawLayout"/>
        /// for use in the property.
        /// </para>
        /// </remarks>
        public IRawLayout Layout { get; set; }

        #endregion // Public Instance Properties

        #region Public Instance Methods

        /// <summary>
        /// Renders the logging event and set the parameter value in the command.
        /// </summary>
        /// <param name="listItem">The entity containing the attribute.</param>
        /// <param name="loggingEvent">The event to be rendered.</param>
        /// <remarks>
        /// <para>
        /// Renders the logging event using this parameters layout
        /// object. Sets the value of the attribute on the entity object.
        /// </para>
        /// </remarks>
        virtual public object GetFormattedValue(LoggingEvent loggingEvent)
        {
            // Format the value
            return Layout.Format(loggingEvent);
        }

        #endregion // Public Instance Methods
    }
}
